#! /usr/bin/env python3

from hazard_value.topo import *
from hazard_value.problem import *
from hazard_value.shuffle_edges import *
from hazard_value.helpers import *
from hazard_value.compute_hazard_value_baseline import *
from hazard_value.compute_hazard_value_strategical import *
from hazard_value.nfa import *

from datetime import datetime as dt
from decimal import *

import argparse
import random
import math
import numpy

# main function of the module

def main():

	formatter = argparse.RawTextHelpFormatter
	parser = argparse.ArgumentParser(formatter_class=formatter)
	parser.description = """Evaluation script for networks with capacities and demands."""

# add the topological arguments

	group = parser.add_argument_group("Topological options")
	group.description = "The options of this group are mutually exclusive."
	exclusive_group = parser.add_mutually_exclusive_group()

	action = group.add_argument("--fat-tree", metavar="n", dest="fat_tree", type=int, help="require a fat tree topology to be used.")
	exclusive_group._group_actions.append(action)
	action = group.add_argument("--bcube", metavar="n", type=int, help="require a bcube topology to be used.")
	exclusive_group._group_actions.append(action)
	action = group.add_argument("topo", metavar="filename", nargs="?", default=None, help = "load the topology to treat from filename.")
	exclusive_group._group_actions.append(action)

# add problem parameter arguments

	group = parser.add_argument_group("Parameter options")
	group.add_argument("-d", type=int, default=1, help="Demand value, default to 1.")
	group.add_argument("--nfa", default=None, help="Custom nfa file.")
	group.add_argument("--rewards", default=None, help="Custom reward file.")
	group.add_argument("-k", type=int, default=float("inf"), help="max number of failed links, default to 0.")
	group.add_argument("-p", type=Decimal, default=Decimal("0.001"), help="Probability of link failure, default to 0.01")
	group.add_argument("-f", type=float, default=0.1, help="rate of flowed pairs, default to 0.1.")

# add the demand options

	group = parser.add_argument_group("Demand options")
	group.description = "The options of this group are mutually exclusive."
	exclusive_group = parser.add_mutually_exclusive_group()

	action = group.add_argument("--demands", default=None, help="Custom demand file.")
	exclusive_group._group_actions.append(action)

	action = group.add_argument("--all-pairs", action="store_true", dest="all_pairs", help="Require the demand to be set between every pair of distinct nodes.")
	exclusive_group._group_actions.append(action)

	action = group.add_argument("--farthest-pairs", action="store_true", dest="farthest_pairs", help="Require the demand to be set between the farthest pair of distinct nodes.")
	exclusive_group._group_actions.append(action)

	action = group.add_argument("--min-degree", dest="min_degree", action="store_true", help="Require the script to use the vertices with the minimal out degree.")
	exclusive_group._group_actions.append(action)

# add the execution options

	group = parser.add_argument_group("Execution options")
	group.add_argument("--logarithmic-weight", dest="logarithmic_weight", action="store_true", help="Require the scenario weight to be logarithmic for the counting problem.")
	group.add_argument("--central-edges", action="store_true", dest="central_edges", help="Request the number of central edges to be printed.")
	group.add_argument("--strategic", action="store_true", help="Request the strategic failure scenario enumeration.")
	group.add_argument("--reorder", type=int, default=0, help="Request edges to be reordered with the strategic search.")
	group.add_argument("--dfs", action="store_true", help="Request edges to be reordered by dfs with the strategic search.")
	group.add_argument("--bfs", action="store_true", help="Request edges to be reordered by bfs with the strategic search.")
	group.add_argument("--float", action="store_true", help="Request a floating point probability..")

# parse arguments

	args = parser.parse_args()
	connectivity_weight = None
	select_vertices = None
	g = None
	W = None
	D = None
	phi = None

# if a fat tree topology is required set it up

	if args.fat_tree: 
		g = compute_fat_tree_topology(args.fat_tree)
		g.vs["label"] = compute_fat_tree_labels(args.fat_tree)
		D, W = compute_fat_tree_demand_groups(args.fat_tree, args.f)
		param = str(args.fat_tree)
		demand = str(len(D))

# otherwise if a bcube topology is required set it up

	elif args.bcube: 
		# g: graph
		g = compute_bcube_topology(args.bcube)
		g.vs["label"] = compute_bcube_labels(args.bcube)
		print("[label] ", g.vs["label"])
		# D/W: Demands & Rewards
		D, W = compute_bcube_demand_groups(args.bcube)
		param = str(args.bcube)
		demand = str(len(D))

# otherwise check that a topology file is provided

	elif args.topo is None:
		parser.print_help()
		exit(1)
	else: 

		g = read_topology(args.topo)
		param = args.topo

# if a nfa file is provided load it

	if args.nfa:
		nfa = load_nfa(args.nfa)
		if args.farthest_pairs: 
			distances = numpy.array(g.shortest_paths())
			D, W = compute_farthest_pair_demand_groups(g, args.d, args.f, distances)
			if args.way_pointing=="single": 
				g.vs["label"] = compute_single_way_pointing_labels(g, W, distances)
			elif args.way_pointing=="double": 
				g.vs["label"] = compute_double_way_pointing_labels(g, W, distances)
			elif not "label" in g.vs.attributes():
				g.vs["label"] = ["." for v in g.vs]
		else:

# check that a demand is well provided

			if args.demands: D = load_demands(args.demands)
			elif not D: raise RuntimeError("No demand was specified.")

# check that a reward function is well provided

			if args.rewards: W = load_rewards(args.rewards)
			elif not W: raise RuntimeError("No reward was specified.")

# if no nfa is provided but that all pair are requested compute the corresponding demand

	elif args.all_pairs:
		W = compute_integral_demand(g, 1 if args.d is None else args.d)

# otherwise if a reward file is provided load it

	elif args.rewards:
		W = load_rewards(args.rewards)
		rewards = args.rewards

# otherwise set a default demand

	else:
		if args.min_degree:
			select_vertices = select_min_degree_vertices
		else :
			select_vertices = select_max_degree_vertices

		W = compute_demand(g, args.f, args.d, uniform_generator, select_vertices)
		rewards = str(g["demand"])

# setup the required precision for the problem

	if args.float: 
		args.p = float(args.p)
	else: 
		getcontext().prec = str(args.p)[::-1].find(".")*len(g.es)*10

# setup the probability function

	if args.logarithmic_weight: 
		phi = compute_scenario_weight
	else : 
		phi = lambda g, f: compute_scenario_probability(g, f, args.p)
	
	if args.k is None: args.k=float("inf")

# setup the connectivity weight function

	if args.strategic: 
		connectivity_weight = compute_hazard_value_strategical
	else: 
		connectivity_weight = compute_hazard_value_baseline

# setup the edge ordering function

	order = lambda g, edges, s, t: shuffle_edges(edges, args.reorder, args.k*args.reorder)
	if args.dfs: order = order_edges_by_dfs
	elif args.bfs: order = order_edges_by_bfs

# perform the experiment for all topologies under each value of k

	t1 = dt.now() # start time
	
	if args.nfa: 
		scenarios, weight, optimum, result = connectivity_weight(g, D, W, nfa, args.k, phi, args.p)
	else: 
		weight, optimum, result = connectivity_weight(g, W, args.k, phi, order)
	
	t2 = dt.now() # end time
	t = t2-t1
	time = float(t.seconds) + float((t).microseconds)/1000000.0

	print (param, end=";")
	print (len(W), end=";")
	print (str(args.p), end=";")
	print (str(args.strategic), end=";")

	if args.dfs: 
		print("dfs", end=";")
	elif args.bfs: 
		print ("bfs", end=";")
	else: 
		print (str(args.reorder), end=";")
	
	print(str(args.k), end=";")

	print("[scenarios] " + str(scenarios), end=";")
	print("[time] %.4f" %time, end=";")
	print("[result] %.8f" %result)

if __name__ == "__main__":
	main()